/*********************************************************************
 *
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2008, 2013, Willow Garage, Inc.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of Willow Garage, Inc. nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 * Author: Eitan Marder-Eppstein
 *         David V. Lu!!
 *********************************************************************/
#ifndef COSTMAP_2D_COSTMAP_2D_H_
#define COSTMAP_2D_COSTMAP_2D_H_

#include <vector>
#include <queue>
#include <geometry_msgs/Point.h>
#include <boost/thread.hpp>

namespace costmap_2d{

struct MapLocation{   // 方便记录位置信息
  unsigned int x, y;
};

//Costmap2D供了世界坐标点与其相关连的“代价”之间的映射
class Costmap2D
{
  friend class CostmapTester;  // 添加友元函数，使 gtest 正常工作
public:
  Costmap2D(unsigned int cells_size_x, unsigned int cells_size_y, double resolution, double origin_x, double origin_y, unsigned char default_value = 0);
  Costmap2D(const Costmap2D& map);
  Costmap2D& operator=(const Costmap2D& map);
  bool copyCostmapWindow(const Costmap2D& map, double win_origin_x, double win_origin_y, double win_size_x, double win_size_y);
  Costmap2D();
  virtual ~Costmap2D();
  unsigned char getCost(unsigned int mx, unsigned int my) const;
  void setCost(unsigned int mx, unsigned int my, unsigned char cost);
  void mapToWorld(unsigned int mx, unsigned int my, double& wx, double& wy) const;
  //作用：从世界坐标(wx, wy)向地图坐标(mx, my)转换,不一定会转换成功，地图坐标系小于世界坐标系，如果转换成功(合法界限)，则为True；否则为false
  // 世界坐标系的度量是米，地图坐标系的度量是像素
  bool worldToMap(double wx, double wy, unsigned int& mx, unsigned int& my) const;
  void worldToMapNoBounds(double wx, double wy, int& mx, int& my) const;              //从世界坐标转换为地图坐标，且不检查是否是合法边界
  void worldToMapEnforceBounds(double wx, double wy, int& mx, int& my) const;         //从世界坐标转换为地图坐标，将结果限制在合法范围内
  inline unsigned int getIndex(unsigned int mx, unsigned int my) const {
    return my * size_x_ + mx;
  }
  inline void indexToCells(unsigned int index, unsigned int& mx, unsigned int& my) const {
    my = index / size_x_;
    mx = index - (my * size_x_);
  }
  unsigned char* getCharMap() const;
  unsigned int getSizeInCellsX() const;
  unsigned int getSizeInCellsY() const;
  double getSizeInMetersX() const;
  double getSizeInMetersY() const;
  double getOriginX() const;
  double getOriginY() const;
  double getResolution() const;
  void setDefaultValue(unsigned char c){
    default_value_ = c;
  }
  unsigned char getDefaultValue(){
    return default_value_;
  }
  bool setConvexPolygonCost(const std::vector<geometry_msgs::Point>& polygon, unsigned char cost_value);
  void polygonOutlineCells(const std::vector<MapLocation>& polygon, std::vector<MapLocation>& polygon_cells);     //获取多边形轮廓在map地图中的单元格
  void convexFillCells(const std::vector<MapLocation>& polygon, std::vector<MapLocation>& polygon_cells);         //获取填充凸多边形的地图单元格
  virtual void updateOrigin(double new_origin_x, double new_origin_y);
  bool saveMap(std::string file_name);                //将代价地图保存到 pgm 文件中，并被命名为 file_name
  void resizeMap(unsigned int size_x, unsigned int size_y, double resolution, double origin_x, double origin_y);
  void resetMap(unsigned int x0, unsigned int y0, unsigned int xn, unsigned int yn);
  unsigned int cellDistance(double world_dist);       //给定世界坐标系下的距离，转换输出为单元格下的距离

  typedef boost::recursive_mutex mutex_t;
  mutex_t* getMutex() {
    return access_;
  }

protected:
  //将源地图的某块区域复制到目标地图(sm_lower_left_x,sm_lower_left_y原地图左下角的xy值，sm_size_x原地图x方向的大小，目标地图也是同理，region_size_x,region_size_y将要复制的地图的区域大小)
  template<typename data_type>
    void copyMapRegion(data_type* source_map, unsigned int sm_lower_left_x, unsigned int sm_lower_left_y, unsigned int sm_size_x,
                       data_type* dest_map,   unsigned int dm_lower_left_x, unsigned int dm_lower_left_y, unsigned int dm_size_x,
                                              unsigned int region_size_x, unsigned int region_size_y){
      data_type* sm_index = source_map + (sm_lower_left_y * sm_size_x + sm_lower_left_x);  //首先需要计算每张地图的起点
      data_type* dm_index = dest_map + (dm_lower_left_y * dm_size_x + dm_lower_left_x);
      for(unsigned int i = 0; i < region_size_y; ++i){                                     //将源地图复制到目标地图中
        memcpy(dm_index, sm_index, region_size_x * sizeof(data_type));
        sm_index += sm_size_x;
        dm_index += dm_size_x;
      }
    }

  virtual void deleteMaps();
  virtual void resetMaps();
  virtual void initMaps(unsigned int size_x, unsigned int size_y);

  template<class ActionType>                                                                  //必须包含头文件#include <limits.h> UINT_MAX=4294967295
    inline void raytraceLine(ActionType at, unsigned int x0, unsigned int y0, unsigned int x1, unsigned int y1, unsigned int max_length = UINT_MAX) {
      int dx = x1 - x0;
      int dy = y1 - y0;
      unsigned int abs_dx = abs(dx);
      unsigned int abs_dy = abs(dy);
      int offset_dx = sign(dx);             // 标记x是前进一格还是后退一个单元格
      int offset_dy = sign(dy) * size_x_;
      unsigned int offset = y0 * size_x_ + x0;
      double dist = hypot(dx, dy);   //根据线条的最大长度来选择缩放主要尺寸的程度， hypot() 返回欧几里德范数 sqrt(x*x + y*y)   需要导入 math 模块
      double scale = (dist == 0.0) ? 1.0 : std::min(1.0, max_length / dist);
      if (abs_dx >= abs_dy){                // 如果 x 占主导地位
        int error_y = abs_dx / 2;
        bresenham2D(at, abs_dx, abs_dy, error_y, offset_dx, offset_dy, offset, (unsigned int)(scale * abs_dx));
        return;
      }
      int error_x = abs_dy / 2;             // y 占主导地位
      bresenham2D(at, abs_dy, abs_dx, error_x, offset_dy, offset_dx, offset, (unsigned int)(scale * abs_dy));
    }

private:
  // Bresenham：布雷森汉姆直线算法，实现了对于离散的平面点，指定两个点，找到两点之间的其他点，使得这些中间组成一个尽可能趋近直线的点集
  // https://zhuanlan.zhihu.com/p/106155534
  // https://blog.csdn.net/dwell548560/article/details/121955098             这个参考比较多
  // https://blog.csdn.net/kakaxi2222/article/details/50708552               还有这个
  // https://blog.csdn.net/cjw_soledad/article/details/78886117              这个也是
  // https://zhuanlan.zhihu.com/p/302806158

  /**********************************
    at -- 用来赋值代价的
    abs_da --起点和终点 x坐标差的绝对值
    abs_db --起点和终点 y坐标差的绝对值
    error_b --  是 起点和终点 x坐标差的绝对值的一半
    offset_a  -- x方向增加的一个单位在map索引下
    offset_b  -- y方向增加的一个单位在map索引下，即一行栅格
    offset -- 起点在map下的索引 用来更新代价值的
    max_length -- 一个尺度量
  ***********************************/
  template<class ActionType>
    inline void bresenham2D(ActionType at, unsigned int abs_da, unsigned int abs_db, int error_b, int offset_a, int offset_b, unsigned int offset, unsigned int max_length) {
      unsigned int end = std::min(max_length, abs_da);
      for (unsigned int i = 0; i < end; ++i){
        at(offset);
        offset += offset_a;
       /*
         * 下面这个情况，error_b + abs_db就会超过abs_da，所以每次x+1后，y也会+1
         *         _
         *       _|
         *     _|
         *   _|
         * _|
         *
         * 下面这个情况，error_b + n* abs_db才会超过abs_da,所以每次x+n后，y才会+1
         *             ___|
         *         ___|
         *     ___|
         * ___|
          */
        // https://blog.csdn.net/TurboIan/article/details/86611713
        error_b += abs_db;  // 为了控制前进上升的斜率
        if ((unsigned int)error_b >= abs_da) {
          offset += offset_b;
          error_b -= abs_da;
        }
      }
      at(offset);
    }

  inline int sign(int x) {
    return x > 0 ? 1.0 : -1.0;
  }

  mutex_t* access_;
protected:
  unsigned int size_x_, size_y_;    // x,y方向像素点个数
  double resolution_;               // 分辨率大小，像素点之间的距离
  double origin_x_, origin_y_;      // 地图原点x,y(左下角)

  // 把地图数据存储在这里，在加载插件的时候(costmap_2d_ros中)，各个插件都有自己的代价数据，被放在这里。
  // 在costmap_2d_ros中创建了一个layerd_costmap实体，而layerd_costmap中又创建了一个costmap_2d类型的实体，
  // 也包含costmap_这个成员，因此，多个不同的实体实际上是共用了costmap_这个成员变量，而layerd_costmap中的costmap_2d类型的实体
  // 被作为master grid来看待，各个分立层，各自使用自己的成员变量costmap_，所以看起来比较复杂，容易蒙圈。利用了多态的细想。
  // costmap_是个指针，在Costmap2D::initMaps中被指向堆中新建的一维数组头
  unsigned char* costmap_;

  unsigned char default_value_;

  class MarkCell{
  public:
    MarkCell(unsigned char* costmap, unsigned char value) : costmap_(costmap), value_(value){}
    inline void operator()(unsigned int offset){
      costmap_[offset] = value_;
    }
  private:
    unsigned char* costmap_;
    unsigned char value_;
  };

  class PolygonOutlineCells{
  public:
    PolygonOutlineCells(const Costmap2D& costmap, const unsigned char* char_map, std::vector<MapLocation>& cells) : costmap_(costmap), char_map_(char_map), cells_(cells){}

    // 将相关单元格添加到列表
    inline void operator()(unsigned int offset) {
      MapLocation loc;
      costmap_.indexToCells(offset, loc.x, loc.y);
      cells_.push_back(loc);
    }

  private:
    const Costmap2D& costmap_;
    const unsigned char* char_map_;
    std::vector<MapLocation>& cells_;
  };
};
}  // namespace costmap_2d

#endif  // COSTMAP_2D_COSTMAP_2D_H
